#include "SWFEncoder.h"
#include "SWF.h"
#include "utils/ImageHelper.h"
#include <QImage>
#include <assert.h>

int minBits(int number, int bits)
{
	int val = 1;
	for (int x = 1; val <= number && !(bits > 32); x <<= 1) 
	{
		val = val | x;
		bits++;
	}

	if (bits > 32)
	{
		//("minBits " + bits + " must not exceed 32");
		assert(false);
	}
	return bits;
}

void SWFEncoder::unknow( TagUnknow* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::doABC( TagDoABC* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::metadata( TagMetadata* tag )
{
	_tagStream->writeString(tag->text);
	this->encodeTag(tag);
}

void SWFEncoder::frameLabel( TagFrameLabel* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::setBackgroundColor( TagSetBackgroundColor* tag )
{
	_tagStream->writeColorRGB(tag->color);
	this->encodeTag(tag);
}

void SWFEncoder::fileAttributes( TagFileAttributes* tag )
{
	UI32 flags = 0;
	if (tag->UseDirectBlit)
		flags |= (1 << 1);
	if (tag->UseGPU)
		flags |= (1 << 2);
	if (tag->HasMetadata)
		flags |= (1 << 3);
	if (tag->ActionScript3)
		flags |= (1 << 4);
	if (tag->UseNetwork)
		flags |= (1 << 7);
	_tagStream->writeUI32(flags);
	this->encodeTag(tag);
}

void SWFEncoder::end( TagEnd* tag )
{
	this->encodeTag(tag);
}

void SWFEncoder::scriptLimits( TagScriptLimits* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::symbolClass( TagSymbolClass* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::importAssets2( TagImportAssets2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::protect( TagProtect* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::exportAssets( TagExportAssets* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::enableDebugger( TagEnableDebugger* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::enableDebugger2( TagEnableDebugger2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::setTabIndex( TagSetTabIndex* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineScalingGrid( TagDefineScalingGrid* tag )
{
	_tagStream->writeUI16(tag->characterID);
	_tagStream->writeRect(tag->Splitter);
	_doneList.push_back(tag);
	this->encodeTag(tag);
}

void SWFEncoder::defineSceneAndFrameLabelData( TagDefineSceneAndFrameLabelData* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineShape( TagDefineShape* tag )
{
	_tagStream->writeUI16(tag->characterID);
	_tagStream->writeRect(tag->ShapeBounds);
	this->encodeShape(tag->code, tag->Shapes);
	this->encodeTag(tag);
}

void SWFEncoder::defineShape2( TagDefineShape2* tag )
{
	this->defineShape(tag);
}

void SWFEncoder::defineShape3( TagDefineShape3* tag )
{
	this->defineShape(tag);
}

void SWFEncoder::defineShape4( TagDefineShape4* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineButton( TagDefineButton* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineButton2( TagDefineButton2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineButtonCxform( TagDefineButtonCxform* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineButtonSound( TagDefineButtonSound* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineSprite( TagDefineSprite* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineMorphShape( TagDefineMorphShape* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineMorphShape2( TagDefineMorphShape2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFont( TagDefineFont* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFont2( TagDefineFont2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFont3( TagDefineFont3* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFont4( TagDefineFont4* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFontInfo( TagDefineFontInfo* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFontInfo2( TagDefineFontInfo2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFontAlignZones( TagDefineFontAlignZones* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineFontName( TagDefineFontName* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineText( TagDefineText* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineText2( TagDefineText2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineEditText( TagDefineEditText* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::cSMTextSettings( TagCSMTextSettings* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::showFrame( TagShowFrame* tag )
{
	this->encodeTag(tag);
}

void SWFEncoder::placeObject( TagPlaceObject* tag )
{
	//TODO: encode PlaceObject
	assert(false);
}

void SWFEncoder::placeObject2( TagPlaceObject2* tag )
{
	_tagStream->writeUI8(tag->flags1);
	_tagStream->writeUI16(tag->Depth);
	if (tag->hasCharID())
	{
		_tagStream->writeUI16(tag->Character->characterID);
	}
	if (tag->hasMatrix())
	{
		_tagStream->writeMatrix(tag->Matrix);
	}
	if (tag->hasCxform())
	{
		//TODO: writeColorTransformWithAlpha
		assert(false);
		//_tagStream->writeColorTransformWithAlpha(tag->ColorTransform);
	}
	if (tag->hasRatio())
	{
		_tagStream->writeUI16(tag->Ratio);
	}
	if (tag->hasName())
	{
		_tagStream->writeString(tag->Name);
	}
	if (tag->hasClipDepth())
	{
		_tagStream->writeUI16(tag->ClipDepth);
	}
	if (tag->hasClipAction())
	{
		//TODO: encode ClipActions
		assert(false);
	}
	this->encodeTag(tag);
}

void SWFEncoder::placeObject3( TagPlaceObject3* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineBitsJPEG2( TagDefineBitsJPEG2* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineBitsJPEG3( TagDefineBitsJPEG3* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::defineBinaryData( TagDefineBinaryData* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::productInfo( TagProductInfo* tag )
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::removeObject(TagRemoveObject* tag)
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::removeObject2(TagRemoveObject2* tag)
{
	this->writeTag(tag, _stream);
}

void SWFEncoder::save( SWF* swf, const std::string& filePath )
{
	_swf = swf;
	_filePath = filePath;
	_doneList.clear();
	_tagStream->reset();
	
	_stream = new SWFOutputStream();
	_stream->writeRect(swf->header.FrameSize);
	_stream->writeUI16(swf->header.FrameRate << 8);
	_stream->writeUI16(swf->header.FrameCount);
	
	swf->fileAttributes->visit(this);
	//enableTelemetry
	if (swf->metadata)
		swf->metadata->visit(this);
	if (swf->setBackgroundColor)
		swf->setBackgroundColor->visit(this);
	if (swf->scriptLimits)
		swf->scriptLimits->visit(this);
	if (swf->protect)
		swf->protect->visit(this);
	if (swf->enableDebugger)
		swf->enableDebugger->visit(this);
	if (swf->enableDebugger2)
		swf->enableDebugger2->visit(this);
	if (swf->defineSceneAndFrameLabelData)
		swf->defineSceneAndFrameLabelData->visit(this);

	TagShowFrame showFrame;
	for (SWFFrame* frame : swf->frames)
	{
		if (frame->frameLabel)
			frame->frameLabel->visit(this);
		//imports
		for (auto tag : frame->imports)
		{
			tag->visit(this);
		}
		//define
		std::vector<DefineTag*> defines;
		frame->getAllDefineTags(defines);
		for (auto tag : defines)
		{
			this->defineTag(tag);
		}
		//exports

		//fonts
		for (auto tag : frame->fonts)
		{
			this->defineTag(tag);
		}
		//doABC
		for (auto tag : frame->doABCs)
		{
			tag->visit(this);
		}
		//control
		for (auto tag : frame->controlTags)
		{
			std::vector<SWFTag*> refs;
			tag->getReferences(refs);
			for (SWFTag* dTag : refs)
			{
				this->defineTag(dTag);
			}
			tag->visit(this);
		}
		//symbol class
		if (frame->symbolClass)
			frame->symbolClass->visit(this);

		showFrame.visit(this);
	}
	TagEnd end;
	end.visit(this);
	
	SWFOutputStream swfStream;
	swfStream.writeBytes("FWS", 3);
	swfStream.writeUI8(swf->header.Version);
	swfStream.writeUI32(_stream->getPosition() + 8);
	swfStream.writeBytes(_stream->getData(), _stream->getPosition());

	FILE* file;
	fopen_s(&file, filePath.c_str(), "wb");
	fwrite(swfStream.getData(), 1, swfStream.getPosition(), file);
	fclose(file);
}

void SWFEncoder::writeTag( SWFTag* tag, SWFOutputStream* stream )
{
	UI32 tagCodeAndLength = tag->code << 6;
	size_t len = tag->length;
	if (len >= 63)
	{
		tagCodeAndLength = tagCodeAndLength | 63;
		_stream->writeUI16(tagCodeAndLength);
		_stream->writeUI32(len);
	}
	else
	{
		tagCodeAndLength = tagCodeAndLength | len;
		_stream->writeUI16(tagCodeAndLength);
	}
	_stream->writeBytes(tag->data, tag->length);
}

void SWFEncoder::defineTag(SWFTag* tag)
{
	auto itr = std::find(_doneList.begin(), _doneList.end(), tag);
	if (itr == _doneList.end())
	{
		//visit refs
		std::vector<SWFTag*> refs;
		tag->getReferences(refs);
		for (SWFTag* dTag : refs)
		{
			this->defineTag(dTag);
		}

		//visit
		_doneList.push_back(tag);
		tag->visit(this);
	}
}

SWFEncoder::SWFEncoder()
	: _stream(nullptr)
	, _swf(nullptr)
{
	_tagStream = new SWFOutputStream();
}

SWFEncoder::~SWFEncoder()
{
	if (_tagStream)
		delete _tagStream;
}

void SWFEncoder::encodeTag(SWFTag* tag)
{
	_tagStream->alignBits();

	ByteArray* bytes = _tagStream->getByteArray();
	UI32 tagCodeAndLength = tag->code << 6;
	size_t len = bytes->getLength();
	if (len >= 63)
	{
		tagCodeAndLength = tagCodeAndLength | 63;
		_stream->writeUI16(tagCodeAndLength);
		_stream->writeUI32(len);
	}
	else
	{
		tagCodeAndLength = tagCodeAndLength | len;
		_stream->writeUI16(tagCodeAndLength);
	}
	_stream->writeBytes(bytes->getData(), len);

	_tagStream->reset();
}

void SWFEncoder::encodeShape( UI8 tagCode, ShapeWithStyle* shape )
{
	shape->FillStyles->encode(_tagStream, tagCode);
	shape->LineStyles->encode(_tagStream, tagCode);
	shape->NumFillBits = minBits(shape->FillStyles->FillStyles.size(), 0);
	shape->NumLineBits = minBits(shape->LineStyles->LineStyles.size(), 0);

	_tagStream->writeUB(shape->NumFillBits, 4);
	_tagStream->writeUB(shape->NumLineBits, 4);
	
	for (ShapeRecord* record : shape->ShapeRecords)
	{
		record->encode(_tagStream, tagCode, shape);
	}
	EndShapeRecord end;
	end.encode(_tagStream, tagCode, shape);
	_tagStream->alignBits();
}